package com.atjinggu.jinggumail.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atjinggu.jinggumail.product.entity.CategoryBrandRelationEntity;
import com.atjinggu.jinggumail.product.service.CategoryBrandRelationService;
import com.atjinggu.jinggumail.product.vo.CateLog2Vo;
import com.atjinggu.jinggumail.product.vo.CateLog3Vo;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atjinggu.jinggumail.common.utils.PageUtils;
import com.atjinggu.jinggumail.common.utils.Query;

import com.atjinggu.jinggumail.product.dao.CategoryDao;
import com.atjinggu.jinggumail.product.entity.CategoryEntity;
import com.atjinggu.jinggumail.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params), new QueryWrapper<CategoryEntity>());

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> queryTree(Map<String, Object> params) {
        List<CategoryEntity> entities = baseMapper.selectList(null);
        List<CategoryEntity> collect = entities.stream().filter(entity -> entity.getParentCid() == 0).map(entity -> {
            entity.setChildren(getChildren(entity, entities));
            return entity;
        }).sorted((entity1, entity2) ->
                (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort())).
                collect(Collectors.toList());
        return collect;
    }

    @Override
    public void deleteMenuIds(List<Long> asList) {
        //TODO 判断当前菜单是否被引用
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] getPaths(Long catelogId) {
        List<Long> paths = new ArrayList();
        getRecursionPath(catelogId, paths);
        Collections.reverse(paths);
        return paths.toArray(new Long[paths.size()]);
    }

    @Caching(evict = {
            @CacheEvict(value = {"category"},key = "'getLevel1Categorys'"),
            @CacheEvict(value = {"category"},key = "'getCategoryTree'")

    })
    //@CacheEvict(value = {"category"},allEntries = true) 删除所有缓存
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        //修改自身表
        this.updateById(category);
        //修改分类与品牌关联关系表
        if(StringUtils.isNotBlank(category.getName())){
            CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
            entity.setCatelogName(category.getName());
            UpdateWrapper<CategoryBrandRelationEntity> wrapper = new UpdateWrapper();
            wrapper.eq("catelog_id",category.getCatId());
            categoryBrandRelationService.update(entity,wrapper);
        }
    }


    public void  getRecursionPath(Long catelogId,List<Long> paths){
        paths.add(catelogId);
        CategoryEntity categoryEntity = this.baseMapper.selectById(catelogId);
        if(categoryEntity.getParentCid() != 0){
            getRecursionPath(categoryEntity.getParentCid(),paths);
        }
    }

    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> entities) {
        List<CategoryEntity> list = entities.stream().filter(a -> {
            return root.getCatId().equals(a.getParentCid());
        }).map(menu -> {
            menu.setChildren(getChildren(menu, entities));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return list;
    }

    @Cacheable(value = {"category"},key = "#root.methodName")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        return this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",0));
    }

    @Cacheable(value = {"category"},key = "#root.methodName")
    @Override
    public Map<String, List<CateLog2Vo>> getCategoryTree(){
            List<CategoryEntity> allList = this.baseMapper.selectList(null);
            //获取一级分类
            List<CategoryEntity> level1Categorys = getCategoryEntities(allList, 0l);
            Map<String, List<CateLog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), a -> {
                List<CategoryEntity> cateLog2 = getCategoryEntities(allList, a.getCatId());
                List<CateLog2Vo> collect2List = cateLog2.stream().map(cate2 -> {
                    CateLog2Vo cateLog2Vo = new CateLog2Vo();
                    cateLog2Vo.setCatalog1Id(cate2.getParentCid());
                    cateLog2Vo.setId(cate2.getCatId());
                    cateLog2Vo.setName(cate2.getName());
                    List<CategoryEntity> cateLog3 = getCategoryEntities(allList, cate2.getCatId());
                    List<CateLog3Vo> collect3List = cateLog3.stream().map(cate3 -> {
                        CateLog3Vo cateLog3Vo = new CateLog3Vo();
                        cateLog3Vo.setCatalog2Id(cate3.getParentCid());
                        cateLog3Vo.setId(cate3.getCatId());
                        cateLog3Vo.setName(cate3.getName());
                        return cateLog3Vo;
                    }).collect(Collectors.toList());
                    cateLog2Vo.setCatalog3List(collect3List);
                    return cateLog2Vo;
                }).collect(Collectors.toList());
                return collect2List;
            }));
            return collect;
    }

    public Map<String, List<CateLog2Vo>> getCategoryTree2(){
        String categoryTree = redisTemplate.opsForValue().get("categoryTree");
        if(StringUtils.isNotEmpty(categoryTree)){
            Map<String, List<CateLog2Vo>> result = JSON.parseObject(categoryTree, new TypeReference<Map<String, List<CateLog2Vo>>>() {
            });
            return result;
        }else {
            Map<String, List<CateLog2Vo>> categoryTreeDB = getCategoryTreeDB_LLock();
            return categoryTreeDB;
        }

    }


    /**
     * 本地锁
     * @return
     */
    public Map<String, List<CateLog2Vo>> getCategoryTreeDB_LLock() {
        //为了防止热点缓存问题需要加锁
        //可以使用this作为锁对象，因为当前thhis是单例的
        synchronized (this) {
            return getCategoryTreeDB_DLock();
        }
    }


    /**
     * 分布式锁
     * @return
     */
    public Map<String, List<CateLog2Vo>> getCategoryTreeDB_DLock() {
        //为了防止热点缓存问题需要加锁
        //可以使用this作为锁对象，因为当前thhis是单例的
        //setIfAbsent 即set nx
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,200,TimeUnit.SECONDS); //锁和过期时间一起设置
        if(lock){
            Map<String, List<CateLog2Vo>> dataFromDB = new HashMap();
            try {
//            redisTemplate.expire("lock",30,TimeUnit.SECONDS); //设置过期时间
                dataFromDB = getDataFromDB();
            }finally {
                //使用redis + lua脚本，原子删锁
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Integer result = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList("lock"), uuid);
//            String lockValue = redisTemplate.opsForValue().get("lock");
//            if(uuid.equals(lockValue)) {
//                redisTemplate.delete("lock");
//            }
            }

            return dataFromDB;
        }else{
            try {
                TimeUnit.MILLISECONDS.sleep(100);  //睡眠100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return  getCategoryTreeDB_DLock();  //synchronized是自旋的，我们这里也要手动自旋
        }
    }


    private Map<String, List<CateLog2Vo>> getDataFromDB() {
        //在此判断缓存中是否有
        String categoryTree = redisTemplate.opsForValue().get("categoryTree");
        if(StringUtils.isNotEmpty(categoryTree)){
            Map<String, List<CateLog2Vo>> result = JSON.parseObject(categoryTree, new TypeReference<Map<String, List<CateLog2Vo>>>() {
            });
            return result;
        }else {
            List<CategoryEntity> allList = this.baseMapper.selectList(null);
            //获取一级分类
            List<CategoryEntity> level1Categorys = getCategoryEntities(allList, 0l);
            Map<String, List<CateLog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), a -> {
                List<CategoryEntity> cateLog2 = getCategoryEntities(allList, a.getCatId());
                List<CateLog2Vo> collect2List = cateLog2.stream().map(cate2 -> {
                    CateLog2Vo cateLog2Vo = new CateLog2Vo();
                    cateLog2Vo.setCatalog1Id(cate2.getParentCid());
                    cateLog2Vo.setId(cate2.getCatId());
                    cateLog2Vo.setName(cate2.getName());
                    List<CategoryEntity> cateLog3 = getCategoryEntities(allList, cate2.getCatId());
                    List<CateLog3Vo> collect3List = cateLog3.stream().map(cate3 -> {
                        CateLog3Vo cateLog3Vo = new CateLog3Vo();
                        cateLog3Vo.setCatalog2Id(cate3.getParentCid());
                        cateLog3Vo.setId(cate3.getCatId());
                        cateLog3Vo.setName(cate3.getName());
                        return cateLog3Vo;
                    }).collect(Collectors.toList());
                    cateLog2Vo.setCatalog3List(collect3List);
                    return cateLog2Vo;
                }).collect(Collectors.toList());
                return collect2List;
            }));
            String treeStr = JSON.toJSONString(collect);
            redisTemplate.opsForValue().set("categoryTree", treeStr, 1, TimeUnit.DAYS);
            return collect;
        }
    }

    private List<CategoryEntity> getCategoryEntities(List<CategoryEntity> allList,long catId) {
       return   allList.stream().filter(item -> item.getParentCid() == catId).collect(Collectors.toList());
    }

}